
/* tcb.h
 * Transactional circular buffer.
 *
 * Copyright (C) 2013 Michael Krivoruchko
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Macro tcb_append handles correctly nested interrupts appending data
 * to the same buffer object. However, if the buffer is used in a threaded
 * environment, the context switch during addition of an element should be
 * avoided.
 * Macros tcb_reserve and tcb_commit allow preallocation of a container
 * for a new element. This lets a writer (in single writer scenario) to
 * avoid extra copy of a new element.
 * Macro tcb_next (in single reader scenario) allows access to the next
 * element of the buffer without extra copy of the element.
 */

#ifndef TCB_H
#define TCB_H

#include <stddef.h>
#include <stdint.h>

#include "core_cm3.h"

#define TCB_MAX_LENGTH ((16 * 1024) - 1)

#define TCB_MAX_PREALLOC 0xf
#define tcb__rcnt(s) ((uint32_t)((s) & 0x3fff))
#define tcb__widx(s) ((uint32_t)(((s) >> 14) & 0x3fff))
#define tcb__wcnt(s) ((uint32_t)(((s) >> 28) & 0x000f))
#define tcb__wclr(s) ((s) & 0xfffffff)
#define tcb__rdec(s) ((s) - 1)
#define tcb__wext(s, n) ({ \
	register uint32_t v = ((s) + 0x10004001); \
	((tcb__widx(v) >= n)? (v & ~(((uint32_t)0x3fff) << 14)): v); \
})

#define TCB_TYPE(buffer_type, elm_type, elm_num) \
typedef struct {  \
	volatile uint32_t state;  \
	elm_type data[elm_num];   \
} buffer_type

#define TCB_CAPACITY(b) (sizeof(b) / sizeof(b[0]))
#define TCB_SIZEOF_ELM(b) (sizeof((b)->data[0]))

/********************************************************************
 * Resets a buffer.
 */
#define tcb_reset(b) ((b)->state = 0)

/********************************************************************
 * Returns true if the buffer has no new element to read.
 */
#define tcb_is_empty(b) ({ \
	register uint32_t st = (b)->state; \
	(tcb__rcnt(st) <= tcb__wcnt(st)); \
})

/********************************************************************
 * Returns true if the buffer has no room to add a new element.
 */
#define tcb_is_full(b) ({ \
	(tcb__rcnt((b)->state) >= TCB_CAPACITY((b)->data)); \
})

/********************************************************************
 * Returns number of elements in the buffer available to read.
 */
#define tcb_length(b) ({ \
	register uint32_t st = (b)->state; \
	(tcb__rcnt(st) - tcb__wcnt(st)); \
})

/********************************************************************
 * Appends a copy of an element to the buffer.
 * This macro returns number of elements in the buffer (including
 * elements currently not yet available for reading) or zero on
 * unsuccessful operation.
 * A new element is added in three steps:
 * 1) reserve a container
 * 2) copy an element into the container
 * 3) make the element available for reader
 */
#define tcb_append(b, v) ({ \
	register uint32_t st = tcb__alloc(&((b)->state), TCB_CAPACITY((b)->data)); \
	if(st > 0) { \
		(b)->data[tcb__widx(st)] = v; \
		if(tcb__wcnt(st) == 1) \
			tcb__commit(&((b)->state)); \
	} \
	tcb__rcnt(st); \
})

/********************************************************************
 * Reserves an element in the buffer.
 * This macro returns a pointer to an allocated element or NULL on
 * unsuccessful operation.
 * This macro should be used in single source scenario. Only one
 * reserved element at time is supported. Each call to tcb_reserve
 * must have corresponding tcb_commit call.
 */
#define tcb_reserve(b, s) ({ \
	s = tcb__alloc(&((b)->state), TCB_CAPACITY((b)->data)); \
	(s > 0)? &((b)->data[tcb__widx(s)]): NULL; \
})

/********************************************************************
 * Makes available to read an element allocated with tcb_reserve.
 * This macro should be used in single source scenario. Only one
 * reserved element at time is supported. Each call to tcb_reserve
 * must have corresponding tcb_commit call.
 */
#define tcb_commit(b, s) ({ \
	if(tcb__wcnt(s) == 1) { \
		register uint32_t st; \
		do { \
			st  = __LDREXW(&((b)->state)); \
		} while(__STREXW(tcb__wclr(st), &((b)->state))); \
	} \
})

/********************************************************************
 * Copy an element into provided container and remove the element from
 * the tail of the buffer.
 * If successful returns non zero value. Otherwise returns zero.
 * Returns one when the last available element has been extracted.
 * Note, that provided container may change regardless of the result
 * of the operation.
 */
#define tcb_extract(b, v) ({ \
	uint32_t st; \
	register uint32_t idx; \
	do { \
		st  = __LDREXW(&((b)->state)); \
		idx = tcb__next(&st, TCB_CAPACITY((b)->data)); \
		if(idx < TCB_CAPACITY((b)->data)) { \
			(v) = (b)->data[idx]; \
		} \
	} while(__STREXW(st, &((b)->state))); \
	(idx < TCB_CAPACITY((b)->data))? (tcb__rcnt(st) - tcb__wcnt(st) + 1): 0; \
})

/********************************************************************
 * Returns a pointer to next element or NULL if the buffer is empty.
 * This macro should not be used with more then one reader of the buffer.
 */
#define tcb_next(b) ({ \
	uint32_t st; \
	register uint32_t idx; \
	do { \
		st  = __LDREXW(&((b)->state)); \
		idx = tcb__next(&st, TCB_CAPACITY((b)->data)); \
	} while(__STREXW(st, &((b)->state))); \
	(idx < TCB_CAPACITY((b)->data))? &((b)->data[idx]): NULL; \
})

/********************************************************************
 * Returns a pointer to indexed element or NULL if index is invalid.
 * Zero indexes an element at the buffer tail.
 * This macro should not be used with more then one reader of the buffer.
 */
#define tcb_get(b, i) ({ \
	register uint32_t idx = tcb__get((b)->state, TCB_CAPACITY((b)->data), i); \
	(idx < TCB_CAPACITY((b)->data))? &((b)->data[idx]): NULL; \
})

/********************************************************************
 * Support functions.
 */
uint32_t tcb__alloc(volatile uint32_t * state, uint32_t elm_num);
void     tcb__commit(volatile uint32_t * state);
uint32_t tcb__next(uint32_t * state, uint32_t elm_num);
uint32_t tcb__get(uint32_t state, uint32_t elm_num, uint32_t idx);

#endif
